// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <unordered_map>

#include "include/v8-platform.h"
#include "src/base/logging.h"
#include "src/base/macros.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/platform.h"
#include "src/base/platform/time.h"
#include "src/base/template-utils.h"
#include "src/base/utils/random-number-generator.h"
#include "src/d8-platforms.h"

namespace v8 {

class PredictablePlatform : public Platform {
public:
    explicit PredictablePlatform(std::unique_ptr<Platform> platform)
        : platform_(std::move(platform))
    {
        DCHECK_NOT_NULL(platform_);
    }

    PageAllocator* GetPageAllocator() override
    {
        return platform_->GetPageAllocator();
    }

    void OnCriticalMemoryPressure() override
    {
        platform_->OnCriticalMemoryPressure();
    }

    bool OnCriticalMemoryPressure(size_t length) override
    {
        return platform_->OnCriticalMemoryPressure(length);
    }

    std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
        v8::Isolate* isolate) override
    {
        return platform_->GetForegroundTaskRunner(isolate);
    }

    int NumberOfWorkerThreads() override { return 0; }

    void CallOnWorkerThread(std::unique_ptr<Task> task) override
    {
        // It's not defined when background tasks are being executed, so we can just
        // execute them right away.
        task->Run();
    }

    void CallDelayedOnWorkerThread(std::unique_ptr<Task> task,
        double delay_in_seconds) override
    {
        // Never run delayed tasks.
    }

    void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override
    {
        // This is a deprecated function and should not be called anymore.
        UNREACHABLE();
    }

    void CallDelayedOnForegroundThread(v8::Isolate* isolate, Task* task,
        double delay_in_seconds) override
    {
        // This is a deprecated function and should not be called anymore.
        UNREACHABLE();
    }

    void CallIdleOnForegroundThread(Isolate* isolate, IdleTask* task) override
    {
        UNREACHABLE();
    }

    bool IdleTasksEnabled(Isolate* isolate) override { return false; }

    double MonotonicallyIncreasingTime() override
    {
        return synthetic_time_in_sec_ += 0.00001;
    }

    double CurrentClockTimeMillis() override
    {
        return MonotonicallyIncreasingTime() * base::Time::kMillisecondsPerSecond;
    }

    v8::TracingController* GetTracingController() override
    {
        return platform_->GetTracingController();
    }

    Platform* platform() const { return platform_.get(); }

private:
    double synthetic_time_in_sec_ = 0.0;
    std::unique_ptr<Platform> platform_;

    DISALLOW_COPY_AND_ASSIGN(PredictablePlatform);
};

std::unique_ptr<Platform> MakePredictablePlatform(
    std::unique_ptr<Platform> platform)
{
    return base::make_unique<PredictablePlatform>(std::move(platform));
}

class DelayedTasksPlatform : public Platform {
public:
    explicit DelayedTasksPlatform(std::unique_ptr<Platform> platform)
        : platform_(std::move(platform))
    {
        DCHECK_NOT_NULL(platform_);
    }

    explicit DelayedTasksPlatform(std::unique_ptr<Platform> platform,
        int64_t random_seed)
        : platform_(std::move(platform))
        , rng_(random_seed)
    {
        DCHECK_NOT_NULL(platform_);
    }

    ~DelayedTasksPlatform()
    {
        // When the platform shuts down, all task runners must be freed.
        DCHECK_EQ(0, delayed_task_runners_.size());
    }

    PageAllocator* GetPageAllocator() override
    {
        return platform_->GetPageAllocator();
    }

    void OnCriticalMemoryPressure() override
    {
        platform_->OnCriticalMemoryPressure();
    }

    bool OnCriticalMemoryPressure(size_t length) override
    {
        return platform_->OnCriticalMemoryPressure(length);
    }

    std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
        v8::Isolate* isolate) override
    {
        std::shared_ptr<TaskRunner> runner = platform_->GetForegroundTaskRunner(isolate);

        base::MutexGuard lock_guard(&mutex_);
        // Check if we can re-materialize the weak ptr in our map.
        std::weak_ptr<DelayedTaskRunner>& weak_delayed_runner = delayed_task_runners_[runner.get()];
        std::shared_ptr<DelayedTaskRunner> delayed_runner = weak_delayed_runner.lock();

        if (!delayed_runner) {
            // Create a new {DelayedTaskRunner} and keep a weak reference in our map.
            delayed_runner.reset(new DelayedTaskRunner(runner, this),
                DelayedTaskRunnerDeleter {});
            weak_delayed_runner = delayed_runner;
        }

        return std::move(delayed_runner);
    }

    int NumberOfWorkerThreads() override
    {
        return platform_->NumberOfWorkerThreads();
    }

    void CallOnWorkerThread(std::unique_ptr<Task> task) override
    {
        platform_->CallOnWorkerThread(MakeDelayedTask(std::move(task)));
    }

    void CallDelayedOnWorkerThread(std::unique_ptr<Task> task,
        double delay_in_seconds) override
    {
        platform_->CallDelayedOnWorkerThread(MakeDelayedTask(std::move(task)),
            delay_in_seconds);
    }

    void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override
    {
        // This is a deprecated function and should not be called anymore.
        UNREACHABLE();
    }

    void CallDelayedOnForegroundThread(v8::Isolate* isolate, Task* task,
        double delay_in_seconds) override
    {
        // This is a deprecated function and should not be called anymore.
        UNREACHABLE();
    }

    void CallIdleOnForegroundThread(Isolate* isolate, IdleTask* task) override
    {
        // This is a deprecated function and should not be called anymore.
        UNREACHABLE();
    }

    bool IdleTasksEnabled(Isolate* isolate) override
    {
        return platform_->IdleTasksEnabled(isolate);
    }

    double MonotonicallyIncreasingTime() override
    {
        return platform_->MonotonicallyIncreasingTime();
    }

    double CurrentClockTimeMillis() override
    {
        return platform_->CurrentClockTimeMillis();
    }

    v8::TracingController* GetTracingController() override
    {
        return platform_->GetTracingController();
    }

private:
    class DelayedTaskRunnerDeleter;
    class DelayedTaskRunner final : public TaskRunner {
    public:
        DelayedTaskRunner(std::shared_ptr<TaskRunner> task_runner,
            DelayedTasksPlatform* platform)
            : task_runner_(task_runner)
            , platform_(platform)
        {
        }

        void PostTask(std::unique_ptr<Task> task) final
        {
            task_runner_->PostTask(platform_->MakeDelayedTask(std::move(task)));
        }

        void PostDelayedTask(std::unique_ptr<Task> task,
            double delay_in_seconds) final
        {
            task_runner_->PostDelayedTask(platform_->MakeDelayedTask(std::move(task)),
                delay_in_seconds);
        }

        void PostIdleTask(std::unique_ptr<IdleTask> task) final
        {
            task_runner_->PostIdleTask(
                platform_->MakeDelayedIdleTask(std::move(task)));
        }

        bool IdleTasksEnabled() final { return task_runner_->IdleTasksEnabled(); }

    private:
        friend class DelayedTaskRunnerDeleter;
        std::shared_ptr<TaskRunner> task_runner_;
        DelayedTasksPlatform* platform_;
    };

    class DelayedTaskRunnerDeleter {
    public:
        void operator()(DelayedTaskRunner* runner) const
        {
            TaskRunner* original_runner = runner->task_runner_.get();
            base::MutexGuard lock_guard(&runner->platform_->mutex_);
            auto& delayed_task_runners = runner->platform_->delayed_task_runners_;
            DCHECK_EQ(1, delayed_task_runners.count(original_runner));
            delayed_task_runners.erase(original_runner);
        }
    };

    class DelayedTask : public Task {
    public:
        DelayedTask(std::unique_ptr<Task> task, int32_t delay_ms)
            : task_(std::move(task))
            , delay_ms_(delay_ms)
        {
        }
        void Run() final
        {
            base::OS::Sleep(base::TimeDelta::FromMicroseconds(delay_ms_));
            task_->Run();
        }

    private:
        std::unique_ptr<Task> task_;
        int32_t delay_ms_;
    };

    class DelayedIdleTask : public IdleTask {
    public:
        DelayedIdleTask(std::unique_ptr<IdleTask> task, int32_t delay_ms)
            : task_(std::move(task))
            , delay_ms_(delay_ms)
        {
        }
        void Run(double deadline_in_seconds) final
        {
            base::OS::Sleep(base::TimeDelta::FromMicroseconds(delay_ms_));
            task_->Run(deadline_in_seconds);
        }

    private:
        std::unique_ptr<IdleTask> task_;
        int32_t delay_ms_;
    };

    std::unique_ptr<Platform> platform_;

    // The Mutex protects the RNG, which is used by foreground and background
    // threads, and the {delayed_task_runners_} map might be accessed concurrently
    // by the shared_ptr destructor.
    base::Mutex mutex_;
    base::RandomNumberGenerator rng_;
    std::unordered_map<TaskRunner*, std::weak_ptr<DelayedTaskRunner>>
        delayed_task_runners_;

    int32_t GetRandomDelayInMilliseconds()
    {
        base::MutexGuard lock_guard(&mutex_);
        double delay_fraction = rng_.NextDouble();
        // Sleep up to 100ms (100000us). Square {delay_fraction} to shift
        // distribution towards shorter sleeps.
        return 1e5 * (delay_fraction * delay_fraction);
    }

    std::unique_ptr<Task> MakeDelayedTask(std::unique_ptr<Task> task)
    {
        return base::make_unique<DelayedTask>(std::move(task),
            GetRandomDelayInMilliseconds());
    }

    std::unique_ptr<IdleTask> MakeDelayedIdleTask(
        std::unique_ptr<IdleTask> task)
    {
        return base::make_unique<DelayedIdleTask>(std::move(task),
            GetRandomDelayInMilliseconds());
    }

    DISALLOW_COPY_AND_ASSIGN(DelayedTasksPlatform);
};

std::unique_ptr<Platform> MakeDelayedTasksPlatform(
    std::unique_ptr<Platform> platform, int64_t random_seed)
{
    if (random_seed) {
        return base::make_unique<DelayedTasksPlatform>(std::move(platform),
            random_seed);
    }
    return base::make_unique<DelayedTasksPlatform>(std::move(platform));
}

} // namespace v8
